
#include <os/scandir.h>
#include <os/mask.h>

#include <cassert>

#include <iostream>
using namespace std;

namespace os
{
    namespace{
        typedef std::set<str>
            SetRegex;
    }
    

    /*
    bool ScanDirectory(const path& absolute_path,
        const SetMask& exclude_files,
        const SetMask& exclude_dirs,
              SetPath& directories,
              SetPath& files,
        const bool regex
        )
    {
        return ScanDirectory(absolute_path, 
            exclude_files, 
            exclude_dirs,
            SetMask(),
            SetMask(),
            directories, 
            files, 
            regex);
    }*/


    void ScanDirectory(const path& absolute_path,
        const SetMask& exclude_files,
        const SetMask& exclude_dirs,
        const SetMask& include_files,
        const SetMask& include_dirs,
              SetPath& directories,
              SetPath& files,
        const bool regex
        )
    {
        #ifdef DEBUG
        const bool ERROR_EXPECTED_ABSOLUTE_PATH = 
            !IsAbsolute(absolute_path);
        assert(!ERROR_EXPECTED_ABSOLUTE_PATH);
        #endif

        const SetMask& excludeF = regex? 
            exclude_files : Mask2Regex(exclude_files);
        const SetMask& excludeD = regex? 
            exclude_dirs  : Mask2Regex(exclude_dirs);

        const SetMask& includeF = regex?
            include_files : Mask2Regex(include_files);
        const SetMask& includeD = regex?
            include_dirs  : Mask2Regex(include_dirs);

        typedef fs::directory_iterator
            It;
        for(It it(absolute_path); it != It(); ++it)
        {
            const fs::path& cur = *it;
            const str name = cur.filename().string();

            const bool directory = IsDir(cur);

            if(directory)
            {
                if(!SelectedByRegex(name, excludeD))
                {
                    if(includeD.empty())
                        directories.insert(cur);
                    else if(SelectedByRegex(name, includeD))
                        directories.insert(cur);
                }
            }
            else
            {
                if(!SelectedByRegex(name,excludeF))
                {
                    if(includeF.empty())
                        files.insert(cur);
                    else if(SelectedByRegex(name, includeF))
                        files.insert(cur);
                }
            }
        }
    }
//-------------------------------------------------------------------------------
//-------------------------------------------------------------------------------
//-------------------------------------------------------------------------------
//-------------------------------------------------------------------------------

    Scanner::Scanner()
        : mExcludeF()
        , mExcludeD()
        , mIncludeF()
        , mIncludeD()
        , mExit(0)
        , mProcF(0)
        , mProcD(0)
    {}

    Scanner& Scanner::ExcludeFileMask(const str& mask)
    { 
        mExcludeF.emplace(Mask2Regex(mask,true)); 
        return *this; 
    }

    Scanner& Scanner::ExcludeFileRegex(const str& regex)
    { 
        mExcludeF.emplace(regex);
        return *this;
    }

    Scanner& Scanner::ExcludeFileMask(const SetMask& list_mask)
    {
        SetMask re = Mask2Regex(list_mask);
        mExcludeF.insert(re.begin(), re.end());
        return *this;
    }

    Scanner& Scanner::ExcludeFileRegex(const SetMask& re)
    {
        mExcludeF.insert(re.begin(),re.end());
        return *this;
    }

//--------------------
    Scanner& Scanner::ExcludeDirMask(const str& mask)
    { 
        mExcludeD.emplace(Mask2Regex(mask,true)); 
        return *this;
    }

    Scanner& Scanner::ExcludeDirRegex(const str& regex)
    { 
        mExcludeD.emplace(regex);
        return *this;
    }

    Scanner& Scanner::ExcludeDirMask(const SetMask& list_mask)
    {
        SetMask re = Mask2Regex(list_mask);
        mExcludeD.insert(re.begin(),re.end());
        return *this;
    }

    Scanner& Scanner::ExcludeDirRegex(const SetMask& list_regex)
    {
        mExcludeD.insert(list_regex.begin(),list_regex.end());
        return *this;
    }
//--------------------

    Scanner& Scanner::IncludeFileMask(const str& mask)
    {
        mIncludeF.emplace(Mask2Regex(mask,true)); 
        return *this;
    }

    Scanner& Scanner::IncludeFileRegex(const str& regex)
    { 
        mIncludeF.emplace(regex); 
        return *this; 
    }

    Scanner& Scanner::IncludeFileMask(const SetMask& list_mask)
    {
        SetMask re = Mask2Regex(list_mask);
        mIncludeF.insert(re.begin(),re.end());
        return *this;
    }

    Scanner& Scanner::IncludeFileRegex(const SetMask& re)
    {
        mIncludeF.insert(re.begin(),re.end());
        return *this;
    }

//--------------------
    Scanner& Scanner::IncludeDirMask(const str& mask)
    { 
        mIncludeD.emplace(Mask2Regex(mask,true)); 
        return *this; 
    }

    Scanner& Scanner::IncludeDirRegex(const str& regex)
    {
        mIncludeD.emplace(regex);
        return *this;
    }

    Scanner& Scanner::IncludeDirMask(const SetMask& list_mask)
    {
        SetMask re = Mask2Regex(list_mask);
        mIncludeD.insert(re.begin(),re.end());
        return *this;
    }

    Scanner& Scanner::IncludeDirRegex(const SetMask& re)
    {
        mIncludeD.insert(re.begin(),re.end());
        return *this;
    }

//--------------------

    Scanner& Scanner::ProcessedFiles(UserFunction func)
    {
        mProcF = func;
        return *this;
    }
    Scanner& Scanner::ProcessedDirectories(UserFunction func)
    {
        mProcD = func;
        return *this;
    }
    Scanner& Scanner::ProcessedExitFromDirectory(ExitFromDirectory func)
    {
        mExit = func;
        return *this;
    }

    void Scanner::Search(const path& absolute_path)
    {
        SetPath d, f;

        ScanDirectory(absolute_path, mExcludeF, mExcludeD, mIncludeF, mIncludeD, d, f, true);

        /*
        cout<< "--- files ---\n";
        for(const auto& i: f)
            cout<< i<<'\n';

        cout<< "--- dirs ---\n";
        for(const auto& i: d)
            cout<< i<<'\n';
        cout<< "[===========]\n";
        */

        if(mProcF && mProcF(f))
            goto stopped;
        
        if(ProccessedFiles(f))
            goto stopped;

        if(mProcD && mProcD(d))
            goto stopped;
        if(ProccessedDirs(d))
            goto stopped;


        for(SetPath::const_iterator i = d.begin(), e = d.end(); i!=e; ++i)
            Search(*i);
    stopped:

        if(mExit)
            mExit(absolute_path);
        LeaveDirectory(absolute_path);
    }
    

}//namespace os

